package mvc;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import renderer.Renderer;
import converter.ConverterFactory;

/**
 * @author LW
 * action 探测器
 */
public class ActionDetector {
	private final Log log = LogFactory.getLog(getClass());
    private ConverterFactory converterFactory = new ConverterFactory();
    private Map<UrlMatcher, Action> getUrlMap 			= new HashMap<UrlMatcher, Action>();
    private Map<UrlMatcher, Action> postUrlMap 			= new HashMap<UrlMatcher, Action>();
    
	/**
     * @param beans
     * @return
     */
    public void awareActionFromBeans(List<Object> beans) {
    	for(Object bean : beans){
    		awareActionFromBean(bean);
    	}
    }
    
    /**
     * @param bean
     * @return
     */
    public void awareActionFromBean(Object bean){
    	Class<?> clazz = bean.getClass();
    	Mapping baseMap = clazz.getAnnotation(Mapping.class);
        String baseUrl = "";
        if(baseMap != null){
        	baseUrl = baseMap.value();
        }
        
        Method[] ms = clazz.getMethods();
        Post post = null;
        Get get = null;
        for (Method m : ms) {
            if (isActionMethod(m)) {
            	String url = baseUrl + m.getAnnotation(Mapping.class).value();
            	UrlMatcher matcher = new UrlMatcher(url,m.getParameterTypes().length);
            	Action action = new Action(bean, m);
            	
            	/*If Post and Get also appeared, choose Get as default*/
            	post = m.getAnnotation(Post.class);
            	get = m.getAnnotation(Get.class);
            	log.info("Mapping url '" + url + "' to method '" + m.toGenericString() + "'.");
            	if(post == null && get == null){
            		getUrlMap.put(matcher, action);
            		postUrlMap.put(matcher, action);
            	} else if(get != null){
            		getUrlMap.put(matcher, action);
            	} else {
            		postUrlMap.put(matcher, action);
            	}
            }
        }
    }
    
    /**
     * check if the specified method is a vaild action method:
     * @param m
     * @return
     */
    boolean isActionMethod(Method m) {
        Mapping mapping = m.getAnnotation(Mapping.class);
        if (mapping==null) {
            return false;
        }
        if (Modifier.isStatic(m.getModifiers())) {
            warnInvalidActionMethod(m, "method is static.");
            return false;
        }
        
        /*check if the url argument type can be convert*/
        UrlMatcher matcher = new UrlMatcher(mapping.value(),m.getParameterTypes().length);
        Class<?>[] argTypes = m.getParameterTypes();
        Class<?> argType;
        for (int argIndex : matcher.orders) {
        	argType = argTypes[argIndex];
            if (!converterFactory.canConvert(argTypes[argIndex])) {
                warnInvalidActionMethod(m, "unsupported argument '" + argType.getName() + "'.");
                return false;
            }
            argTypes[argIndex] = null;
        }
        /*
         * In addition to url-argument, all other argument can not be primitive type.
         * This is to avoid null pointer exceptions
         */
        for(Class<?> type : argTypes){
        	if(type != null){
        		if(type.isPrimitive()){
        			warnInvalidActionMethod(m, "In addition to url-argument, all other argument can not be primitive type");
        			return false;
        		}
        		
        		if(type.isArray() && !converterFactory.canConvert(type.getComponentType())){
        			warnInvalidActionMethod(m, "unsupport array type "+ type.getName() +".");
        			return false;
        		}
        	}
        }
        
        Class<?> retType = m.getReturnType();
        if (retType.equals(void.class) || retType.equals(String.class) || Renderer.class.isAssignableFrom(retType)){
        	return true;
        }
        
        warnInvalidActionMethod(m, "unsupported return type '" + retType.getName() + "'.");
        return false;
    }

 // log warning message of invalid action method:
    void warnInvalidActionMethod(Method m, String string) {
        log.warn("Invalid Action method '" + m.toGenericString() + "': " + string);
    }
    
    public Map<UrlMatcher, Action> getGetUrlMap() {
    	return getUrlMap;
    }
    
    public Map<UrlMatcher, Action> getPostUrlMap() {
    	return postUrlMap;
    }
}